#include "tetraquad.h"
#include <QGLShader>
#include <QVector3D>

#ifdef __APPLE__
    #include <OpenGL/gl.h>
    #include <OpenGL/glext.h>
#else
    #include <GL/gl.h>
#endif

TetraQuad::TetraQuad(const QVector3D& center, QObject *parent):
    Object3D(center, parent)
{
#if   defined(__APPLE__)
    setInputType(GL_LINES_ADJACENCY_EXT);
#elif defined(__WIN32__)
    setInputType(GL_LINES_ADJACENCY_EXT);
#else
    setInputType(GL_LINES_ADJACENCY);
#endif
    buildObject();
}

TetraQuad::TetraQuad(const TetraQuad& tt): Object3D(tt)
{
    buildObject();
}

Object3D* TetraQuad::copy() const
{
    return new TetraQuad(*this);
}

TetraQuad::~TetraQuad()
{
}

void TetraQuad::drawGeometry(void)
{
    if(!show)
        return;

    if(points.size() > 0)
    {
        glDisable(GL_LIGHTING);
        glColor3f(1.0, 0.0, 1.0);
        glBegin(GL_POINTS);
        {
            for(int i = 0; i < points.size(); ++i)
            {
                glVertex4fv(reinterpret_cast<const GLfloat *>(&(points[i])));
            }
        }glEnd();
        glEnable(GL_LIGHTING);
    }
    else
    {
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

        program.bind();
        //program.release();

        program.setUniformValue("Q0", quadrics[0]);
        program.setUniformValue("Q1", quadrics[1]);
        program.setUniformValue("Q2", quadrics[2]);
        program.setUniformValue("Q3", quadrics[3]);

        glBegin(inputType());
        {
            for(int i = 0; i < 4; ++i)
            {
//                QVector4D q0 = quadrics[i].row(0);
//                QVector3D q1 = QVector3D( quadrics[i](1,1), quadrics[i](1,2), quadrics[i](1,3) );
//                QVector3D q2 = QVector3D( quadrics[i](2,2), quadrics[i](2,3), quadrics[i](3,3) );
//                program.setAttributeValue("Q_00_01_02_03", q0);
//                program.setAttributeValue("Q_11_12_13", q1);
//                program.setAttributeValue("Q_22_23_33", q2);
                glVertex4fv(reinterpret_cast<const GLfloat *>(&(vertices[i])));
            }
        }glEnd();

        program.release();
    }
}

void TetraQuad::buildObject()
{
    show = true;
    float t[16] = { -1.0, -1.0, -1.0, 1.0,
                     1.0,  1.0, -1.0, 1.0,
                     1.0, -1.0,  1.0, 1.0,
                    -1.0,  1.0,  1.0, 1.0};

    vertices.append(QVector4D(t[4*0 + 0], t[4*0 + 1], t[4*0 + 2], t[4*0 + 3]));
    vertices.append(QVector4D(t[4*1 + 0], t[4*1 + 1], t[4*1 + 2], t[4*1 + 3]));
    vertices.append(QVector4D(t[4*2 + 0], t[4*2 + 1], t[4*2 + 2], t[4*2 + 3]));
    vertices.append(QVector4D(t[4*3 + 0], t[4*3 + 1], t[4*3 + 2], t[4*3 + 3]));

    w.setColumn(0,vertices[0]);
    w.setColumn(1,vertices[1]);
    w.setColumn(2,vertices[2]);
    w.setColumn(3,vertices[3]);
    w = w.inverted();

    quadrics.append(SPHERE);
    quadrics.append(SPHERE);
    quadrics.append(SADDLE);
    quadrics.append(SADDLE);

    QGLShader *vert = new QGLShader(QGLShader::Vertex  );
    QGLShader *geom = new QGLShader(QGLShader::Geometry);
    QGLShader *frag = new QGLShader(QGLShader::Fragment);
    qDebug() << vert->compileSourceFile(":/vertT");
    qDebug() << vert->log();
    qDebug() << geom->compileSourceFile(":/geomT");
    qDebug() << geom->log();
    qDebug() << frag->compileSourceFile(":/fragT");
    qDebug() << frag->log();

    program.addShader(vert);
    program.addShader(geom);
    program.addShader(frag);
    program.setGeometryInputType(inputType());
    program.setGeometryOutputType(GL_TRIANGLE_STRIP);

    qDebug() << program.link();
    qDebug() << program.log();
}

QVector<QVector4D> TetraQuad::getVertices()
{
    return vertices;
}

void TetraQuad::setQuadric(const QMatrix4x4& Q, int idx)
{
    quadrics[idx] = Q;
}

QVector4D TetraQuad::bariCoord(const QVector4D& p)
{
    return w*p;
}

float TetraQuad::eval(const QVector4D& p)
{
    QVector4D bC = bariCoord(p);
    QMatrix4x4 Q = quadrics[0]*bC.x() + quadrics[1]*bC.y() + quadrics[2]*bC.z() + quadrics[3]*bC.w();
    return QVector4D::dotProduct(p,Q*p);
}

float TetraQuad::evalByBari(const QVector4D& bC, QVector4D& p)
{
    p = vertices[0]*bC.x() + vertices[1]*bC.y() + vertices[2]*bC.z() + vertices[3]*bC.w();
    QMatrix4x4 Q = quadrics[0]*bC.x() + quadrics[1]*bC.y() + quadrics[2]*bC.z() + quadrics[3]*bC.w();
    return QVector4D::dotProduct(p,Q*p);
}

void TetraQuad::buildPointCloud()
{
    points.clear();

    QVector<QVector4D> ret;

    int numLayers = 100;
    float d = 1.0/numLayers;
    for(float r = -1.0; r <= 1.0; r += d)
    {
        for(float s = -1.0; s <= 1.0; s += d)
        {
            for(float t = -1.0; t <= 1.0; t += d)
            {
                if(r+s+t >= 1.0)
                {
                    continue;
                }

                float u = 1.0 - (r+s+t);

                QVector4D p;
                float f = evalByBari(QVector4D(r,s,t,u),p);
                if( qAbs(f) < 0.01 )
                    ret.append(p);
            }
        }
        qDebug() << (r+1.0)*100.0/2.0 << "%";
    }
    points = ret;
}

bool TetraQuad::isProgramLinked()
{
    return program.isLinked();
}

void TetraQuad::setShow(bool v)
{
    show = v;
}
